# -*- coding: utf-8 -*-

"""

This module provides a spritesystem. Its goal was to implement a spritesystem that has all desired
capabilities but it is still easy to use. So far following features have been implemented::


    - world and screen coordinate system (sprites position is in world coord, sprites rect attribute
        is in screen coord, so the rect collision methods can cope with the number of rendered sprites,
        but would be unusable in a large world with many more sprites that are not rendered)
        conversion methods can be customized in the camera class (the default conversions are implemented)
    - easy sprite picking to find out which sprites are in a certain area on screen
    - easy way to implement a special render path through a custom 'draw(...)' method of the sprite
        (its not the default because of performance, each method call costs)
    - interpolated rendering to be used with a fixed timestep logic, not done by default because
        of performance, reasons to use fixed step: http://gafferongames.com/game-physics/fix-your-timestep/
    - push/pop sprites for scene management or splashscreens without the need to
        save current->clear->add new->clear new->re-add previous sprites
    - simple way to render a hud (heads up display)

    - scrolling by a camera system of the known sprites (which sprites are in the visible area should
        be determined by the game logic)
    - camera tracking a sprite or any object
    - multiple cameras for split screen or multi view output of a world

    - floating point precision positions
    - anchor system using an offset
    - easy transformations (conbinations also allowed):
            - rotation
            - zoom
            - flipping in x and y direction
            - alpha (even with per pixel transparency)
    - image caching of sprites for performance, not sure how benefitial this is!
    - easy layer implementation using a z_index
    - parallax scrolling (independent of layer and each axis can be configured independently)
    - soure rect support
    - blit flags support


    - own vector implementation
    - easy text sprites
    - easy vector drawing sprites

Performance was also a major concern, so the default usage is optimized and therefore the code might
look a bit strange at certain places. For the same reason the sprite has some attributes that should not
be changed directly, only through a method (properties are slow too, unfotrunately).


Things that do not belong into a spritesystem:

    - animation
    - collision detection (except picking because only the renderer actually knows where on the screen the sprites are)
    - particle system
    - path following
    - tweening
    - camera visibility determination in the world using a spatial hash, quadtree, etc (gos into gamelogic)

notes:


    - Groups: maybe a simply group to apply some function to all of its sprites at once (those groups might differ from
        the renderlist or there might be multiple groups with overlapping contents...)


Usecases:

    - simple, single screen game, no scrolling
    - split screen game in the same world or different worlds
    - scrolling games (with and without parallax scrolling)
    - custom drawing of sprites
    - custom camera conversions
    -


This module contains three main classes and some derived ones. Those three main classes are:

    - Camera
    - DefaultRenderer
    - Sprite

The sprites are added to the renderer and the camera is used to make a part of the game-world
visible on a certain screen area.

# pylint: correct pylint errors:   pylint --rcfile=HG_google_python-pyknic\pyknic\pylint.config -d E0202 spritesystem.py



.. todo:: 

    - use subpixel surfaces for sprites as default, make a disable switch (memory!)
    - draw only N layers (default to all layers): lower_layer <= layers <= upper_layers
    - use position of entity and a sprite to render (remove 'position' from sprite, sprite is only for rendering!) -> maybe draw(entities)  where entity has: pos_x, pos_y, sprite attributes (hmm, entity has ref to sprite, sprite has ref to entity???)
    - sub sprites, hierarchy of sprites to render (all belonging together, if parent is moved all move, etc. ... usecase???)
    - minimal signal/event class so z_layer changes and other things can be notified
    - remove vector class dependency! use spr.pos_x, spr.pos_y or named tuple maybe (just plain python constructs! Performance!)
    - sprite image get/set for animation or animation sprite (image should be a property of the source image, makes sprite dirty if changed)
    - dirty event causes listening renderers to add this sprite to a dirty list (but only once, how? using set() ?)
    - sprite has no logic for rendering/manipulating image, only the renderer does, should the renderer cache the images?


"""

__all__ = ["Sprite", "TextSprite", "VectorSprite", "Camera", "DefaultRenderer", "HudRenderer"]

import pygame

from pyknicpygame.pyknic.mathematics import Vec2 as Vec
from pyknicpygame.pyknic.mathematics import TYPEPOINT, TYPEVECTOR, Point2


class Camera(object):
    """
    The camera class used to determine the visual area in the world and on screen.
    It could be used by an algorithm to determine which sprites should
    be added to the renderer and which should not. The camera does not manage
    the visible sprites.

    """

    use_conversion_methods = False
    padding = 50

    def __init__(self, screen_rect, position=None, padding=None):
        """
        :Parameters:

            screen_rect : pygame.Rect
                The screen area in screen coordinates
            position[None] : Vector
                Position in world coordinates
            padding[None] : int
                Padding in world coordinates, it defaults to a 50 wide border
        """
        # viewport
        self.offset = Vec(0.0, 0.0)
        self._screen_rect = None
        self._set_screen_rect(screen_rect)

        if padding:
            self.padding = padding

        # position
        self._position = Point2()
        self.old_position = Point2()
        if position:
            assert position.w == TYPEPOINT
            self._position = position
            self.old_position = Vec(self._position.x, self._position.y)

        self.tracker = None

    @property
    def track(self):
        """
        Returns the tracked object in the world. The tracked object
        only needs a position attribute returning a Vector.
        """
        return self.tracker

    @track.setter
    def track(self, obj):
        """
        Set the object of the world to be tracked.
        """
        self.tracker = obj

    def is_tracking(self):
        """
        Returns true if the camera is tracking an object.
        """
        return (self.tracker is not None)

    @property
    def position(self):
        """
        The position of the camera in world coordinates.
        """
        if self.tracker:
            self.old_position = self._position
            self._position = self.tracker.position
        return self._position

    @position.setter
    def position(self, pos):
        """
        Set the position the cam should center on in world coordinates. Resets the tracked object to None.
        """
        self.tracker = None
        self.old_position = self._position
        self._position = pos

    def _get_screen_rect(self):
        """
        Returns a copy of the screen rect in screen coordinates.
        """
        return pygame.Rect(self._screen_rect)

    def _set_screen_rect(self, value):
        """
        Set the screen rect in screen coordinates.
        """
        self._screen_rect = value
        self.offset = Vec(-self._screen_rect.w / 2 - value.left, -self._screen_rect.h / 2 - value.top)
        
    rect = property(_get_screen_rect, _set_screen_rect, doc="""Gets or sets the screen rect.""")

    @property
    def world_rect(self):
        """
        world_rect in world coordinates. Use attribute 'rect' for a rect in screen coordinates.
        This is the visible area of the world. It will be centered upon the cameras position and padded
        with a border or size of the padding attribute (default: 50).

        :returns: Rect in world coordinates.
        """
        border = 2 * self.padding
        world_rect = self._screen_rect.inflate(border, border)
        world_rect.center = (self._position.x, self._position.y)
        return world_rect

    def world_to_screen(self, world_pos, parallax_factors=Vec(1.0, 1.0), offset=Vec(0.0, 0.0)):
        """
        Converts world to screen coordinates.

        :Parameters:
            world_pos : Vector
                world coordinates to convert
            parallax_factors : Vector
                The parallax factor to use during the conversion
            offset[Vector(0, 0)] : Vector
                offset in screen coordinates, used for the sprite offset

        :Returns:
            The screen coordinates as a Vector.
        """
        # todo: interpolation factor here also?
        # return world_pos - self._position % parallax_factors - self.offset - offset
        return Vec(world_pos.x - self._position.x * parallax_factors.x - self.offset.x - offset.x,
                   world_pos.y - self._position.y * parallax_factors.y - self.offset.y - offset.y)

    def screen_to_world(self, screen_pos, parallax_factors=Vec(1.0, 1.0), offset=Vec(0.0, 0.0)):
        """
        Converts a screen coordinate to world coordinate.

        :Parameters:
            screen_pos : Vector
                screen coordinates to convert
            parallax_factors : Vector
                The parallax factor to use during the conversion
            offset[Vector(0, 0)] : Vector
                offset in screen coordinates, used for the sprite offset

        :Returns:
            The world coordinates as a Vector.
        """
        # return screen_pos  + offset + self.offset + self._position % parallax_factors
        return Vec(screen_pos.x  + offset.x + self.offset.x + self._position.x * parallax_factors.x,
                   screen_pos.y  + offset.y + self.offset.y + self._position.y * parallax_factors.y)

class DefaultRenderer(object):
    """
    The base rendering class. Its only purpose is to hold a list of sprites and render them onto
    the screen. By doing so the sprites rect attribute is updated to reflect the screen coordinate.

    :note: sprites that are not rendered do not update their rect attribute!
    """

    def __init__(self):
        """
        Constructor.
        """
        self._sprites = []
        self._sprite_stack = []
        self.need_sort = True

    def add_sprite(self, spr):
        """
        Add a single sprite. Any added sprite is rendered.
        A sprite can only be added once!
        """
        if spr not in self._sprites:
            self._sprites.append(spr)
            self.update_z_layer(spr)

            # # todo: bisect.insort_right -> is this faster than just sorting the list as in update_z_layers
            # x_internal_z = spr.internal_z
            # a = self._sprites
            # lo = 0
            # hi = len(a)
            # while lo < hi:
                # mid = (lo+hi) // 2
                # if x_internal_z < a[mid].internal_z:
                    # hi = mid
                # else:
                    # lo = mid+1
            # a.insert(lo, spr)

    def add_sprites(self, new_sprites):
        """
        Add multiple sprites at once. Any added sprite will be rendered.
        A sprite can only be added once!

        :Parameters:
            new_sprites : iterable
                any iterable containing sprites
        """
        for spr in new_sprites:
            # self.add_sprite(spr)
            if spr not in self._sprites:
                self._sprites.append(spr)
        self.update_z_layers()

    def remove_sprite(self, spr):
        """
        Remove the given sprite.

        :Parameters:
            spr : Sprite
                the sprite to remove

        :Returns:
            True if the sprite could be removed, otherwise False

        """
        try:
            self._sprites.remove(spr)
            return True
        except ValueError:
            return False

    def clear(self):
        """
        Remove all sprites at once.
        """
        self._sprites = []

    def push_sprites(self, new_sprites):
        """
        Push the current sprites onto the internal stack and add the new sprites.
        """
        self._sprite_stack.append(self._sprites)
        self.clear()
        self.add_sprites(new_sprites)

    def pop_sprites(self):
        """
        Clear the current sprites and pop the internal sprite stack back as active sprites.

        :Returns:
            the currently active sprites that are removed
        """
        old_sprites = []
        if self._sprite_stack:
            old_sprites = self._sprites
            self._sprites = self._sprite_stack.pop(-1)
        else:
            self.clear()
        return old_sprites

    def get_sprites(self):
        """
        Returns a list with all currently drawn sprites.
        """
        return list(self._sprites)

    def update_z_layer(self, spr, z_layer=None):
        """
        Update the z layer of the sprite.

        :Parameters:
            spr : Sprite
                the sprite to update, the z_layer attribute of the sprite is used if not
                overriden by the z_layer argument
            z_layer[None] : float
                if set, then this z_layer index is set on the sprite
        """
        if z_layer:
            spr.z_layer = z_layer
        # TODO: remove and re-insert sprite using bisect? faster than re-sorting?
        # self.remove_sprite(spr)
        # self.add_sprite(spr)
        self.update_z_layers()

    def update_z_layers(self):
        """
        Sort all sprites by their z_layer attribute.
        """
        self._sprites.sort(key = lambda e: e.internal_z)

    def draw(self, surf, cam, fill_color=None, do_flip=False, interpolation_factor=1.0):
        """
        Draws the sprites and updates theis rect attribute to reflect their screen position.

        :Parameters:
            surf : Surface
                the surface to render onto
            cam : Camera
                the camera to use to render (for scrolling and viewport)
            fill_color[None] : tuple
                if set to a rgb tuple then the area given by the cam will be filled with it on the given surface
            do_flip[False] : bool
                if set to True, do a display flip at the end of rendering
            interpolation_factor[1.0] : float
                the interpolation factor to use, should be in the range [0, 1.0), other values might work too but
                the result might be unwanted

        """

        surf.set_clip(cam.rect)

        if fill_color is not None:
            surf.fill(fill_color)

        surf_blit = surf.blit

        if cam.use_conversion_methods:
            camera_world_to_screen = cam.world_to_screen
            for spr in self._sprites:
                if spr.draw_special:
                    # custom render path
                    spr.rect = spr.draw(surf, cam, interpolation_factor)
                    assert spr.rect is not None
                else:
                    center = camera_world_to_screen(spr.position, spr.parallax_factors, spr.offset)
                    spr.rect.center = (center.x, center.y)
                    surf_blit(spr.image, spr.rect, spr.area, spr.surf_flags)
        else:
            camera_offset_x = cam.offset.x
            camera_offset_y = cam.offset.y

            if 1.0 == interpolation_factor:
                camera_position_x = cam.position.x
                camera_position_y = cam.position.y
                for spr in self._sprites:
                    if spr.draw_special:
                        spr.rect = spr.draw(surf, cam, interpolation_factor)
                        assert spr.rect is not None
                    else:
                        assert spr.position.w == TYPEPOINT, \
                                "sprite possition should be a point " + str(spr.position)
                        # previousState + interpolation_factor * (currentState - previousState)
                        spr.rect.centerx = spr.position.x - camera_offset_x - \
                                                        camera_position_x * spr.parallax_factors.x - spr.offset.x
                        spr.rect.centery = spr.position.y - camera_offset_y - \
                                                        camera_position_y * spr.parallax_factors.y - spr.offset.y

                        surf_blit(spr.image, spr.rect, spr.area, spr.surf_flags)
            else:
                camera_position_x = cam.old_position.x + interpolation_factor * (cam.position.x - cam.old_position.x)
                camera_position_y = cam.old_position.y + interpolation_factor * (cam.position.y - cam.old_position.y)
                for spr in self._sprites:
                    if spr.draw_special:
                        spr.rect = spr.draw(surf, cam, interpolation_factor)
                        assert spr.rect is not None
                    else:
                        assert spr.position.w == TYPEPOINT, \
                                "sprite possition should be a point " + str(spr.position)
                        # previousState + interpolation_factor * (currentState - previousState)
                        spr_pos_x = spr.old_position.x + \
                                        interpolation_factor * (spr.position.x - spr.old_position.x)
                        spr_pos_y = spr.old_position.y + \
                                        interpolation_factor * (spr.position.y - spr.old_position.y)

                        spr.rect.centerx = spr_pos_x - camera_offset_x - \
                                                        camera_position_x * spr.parallax_factors.x - spr.offset.x
                        spr.rect.centery = spr_pos_y - camera_offset_y - \
                                                        camera_position_y * spr.parallax_factors.y - spr.offset.y

                        surf_blit(spr.image, spr.rect, spr.area, spr.surf_flags)

        surf.set_clip(None)
        if do_flip:
            pygame.display.flip()

    def remove_offscreen_sprites(self, cameras):
        """
        Removes all sprites that are outside the cameras screen rects.

        :Parameters:
            cameras : list of Camera
                the cameras to check against

        :returns: a list with the removed sprites

        """
        removed = self.get_offscreen_sprites(cameras)
        for spr in removed:
            self.remove_sprite(spr)
        return removed

    def get_offscreen_sprites(self, cameras):
        """
        Returns the sprites that are not visible through any camera.

        :Parameters:
            cameras : list of Camera
                the cameras to check against

        :returns: a list of Sprites which are in the renderer but outside of the visible area of the cameras.
        """
        all_sprites = set(range(len(self._sprites)))
        indices = set()
        for cam in cameras:
            indices.update(cam.rect.collidelistall(self._sprites))
        return [self._sprites[idx] for idx in (all_sprites - indices)]

    # todo: how to pick invisible sprites?
    def get_sprites_in_rect(self, screen_rect, do_reverse=True):
        """
        Get the colliding sprites in the given rect in screen coordinates. Using the do_reverse argument
        the sort order of the sprites can be reversed. By default the first sprites it the topmost.

        :note: only visible the sprites known to the renderer are checked (because the rect attribute of
            te other sprites is not updated)

        :Parameters:
            screen_rect : Rect
                rect in screen coordinates used to determine colliding sprites
            do_reverse[True] : bool
                if True then the first sprites is the topmost, otherwise its the last one.
        """
        indices = screen_rect.collidelistall(self._sprites)
        if do_reverse:
            return [self._sprites[idx] for idx in reversed(indices)]
        else:
            return [self._sprites[idx] for idx in indices]


class HudRenderer(DefaultRenderer):
    """
    This is a simple renderer that does not respect scrolling (the cameras position is ignored).
    """

    def __init__(self):
        """
        Constructor.
        """
        DefaultRenderer.__init__(self)

    def draw(self, surf, cam, fill_color=None, do_flip=False, interpolation_factor=1.0):
        """
        See DefaultRenderer.draw() for documentation.
        """
        if self.need_sort:
            self._sprites.sort(key = lambda e: e.internal_z)
            self.need_sort = False

        surf.set_clip(cam.rect)
        if fill_color is not None:
            surf.fill(fill_color)

        surf_blit = surf.blit

        for spr in self._sprites:
            if spr.draw_special:
                spr.rect = spr.draw(surf, cam, interpolation_factor)
                assert spr.rect is not None
            else:
                assert spr.position.w == TYPEPOINT, \
                            "sprite possition should be a point " + str(spr.position)

                spr.rect.centerx = spr.position.x - spr.offset.x
                spr.rect.centery = spr.position.y - spr.offset.y

                surf_blit(spr.image, spr.rect, spr.area, spr.surf_flags)

        surf.set_clip(None)
        if do_flip:
            pygame.display.flip()


class Sprite(object):
    """
    The sprite class. A sprite mainly stores following attributes:

        - position in world coordinates
        - image
        - rotation
        - flip (x- and y-axis)
        - zoom
        - rect in screen coordinates (position on screen used in the blit method, see note)
        - anchor
        - alpha transparency (even on a per pixel transparent image)
        - (source-) area (for the blit method)
        - surf_flags (for the blit method)
        - parallax_factors
        - z_layer
        - old_position (for interpolation only, see note)

    :note: the rect attribute will only be updated if the sprite is rendered.
    :note: if using interpolation when rendering then the update logic of the game should take care
            to set the old_position too additionally to setting the position

    The sprite class uses a image cache to cache images to avoid doing image transformations in
    each frame. This works best if many sprites share the same image and a discrete set of angle and
    scale factors are used. For this reasong there is also a rotation precision and zoom precision which
    determines how many digits for a angle will be used. The number of cached images is limited (to limit
    memory usage). When this limit is reached the cache is cleared. This limit can be customized.The image
    cache can also be disabled completely. It is disabled per default.
    """

    # renderer = DefaultRenderer()
    area = None
    surf_flags = 0
    draw_special = False

    # following attributes are used internally by the renderer
    # for performance reasons they are public, but they should
    # not be used directly
    internal_z = 0 # internal use only
    parallax_factors = Vec(1.0, 1.0)
    offset = Vec(0.0, 0.0) # internal use only

    # caching: not sure how beneficial caching really is
    _use_image_cache = False
    # how many digites after point to round angle to,
    # e.g. 0 means 1 degree precision, 1 means 0.1 degree precision
    _rotation_precision = 0
    _zoom_precision = 2
    # max number of entries before cache will be cleared
    _max_cache_entry_count = 1000
    _image_cache = {}

    _fixed_point = 'center' # same as anchor, internal use only
    _alpha = 255
    _rot = 0.0 # internal use only
    _flip_x = False # internal use only
    _flip_y = False # internal use only
    _zoom_factor = 1.0 # internal use only

    def __init__(self, image, position, anchor=None, z_layer=None, parallax_factors=None, do_init=True):
        """

        :Parameters:
            image : Surface
                the image to use for that sprite, use 'set_image' to change the image later and do not set the
                image attribute directly (its because of the transformations like rotation and zoom)
            position : Vector
                the position of the entitiy in world coordinates
            anchor['center'] : rect attribute name or Vector
                anchor point, defaults to 'center' but can be one of following:
                    topleft, midtop, topright, midright, bottomright, midbottom, bottomleft, midleft, center
                    or a Vector (in sprite coordinates) defining the offset from the sprite center
            z_layer[0] : float or int
                the layer this sprite is in, lower values is 'farther away'
            parallax_factors[Vector(1.0, 1.0)] : Vector
                a vector containin two floats, normally in the range [0.0, 1.0], if set to 0.0 then
                the sprite will not scroll (kinda attached to the camera), this works good for single
                screen games using one camera (for split screen with multiple cameras this wont work because
                each screen should have its own HUD sprites, therefore the HUDRenderer should be used)
            do_init[True] : bool
                if set to True, initial _update() is called, otherwise not

        :Attributes:
            area[None] : Rect
                the source area to be used by the blit method
            surf_flags[0] : const
                this corresponds to the special_flags of the blit command, any of the BLEND_* constants of pygame
            draw_special[False] : bool
                if set to True the renderer will call the sprites draw method instead of bliting the sprite
                using its attributes. This is a special render path.


        """
        self.rect = pygame.Rect(0, 0, 0, 0)

        # TODO: rename image to something else and make image a get property only
        self.image = None
        self._image = None

        if image:
            self.image = image.copy()
            self._image = image
            self.rect = image.get_rect() # screen rect, updated by renderer
            # self.on_image_changed()

        if anchor:
            self._fixed_point = anchor
            # self.on_anchor_changed()

        assert position.w == TYPEPOINT, "sprite possition should be a point " + str(position)
        self.position = position
        self.old_position = Vec(self.position.x, self.position.y)

        if z_layer:
            self.internal_z = z_layer
            # self.on_z_layer_changed()

        if parallax_factors:
            self.parallax_factors = parallax_factors

        if do_init:
            self._update()

    @property
    def anchor(self):
        """
        Returns the value of the anchor, can be one of:
            topleft, midtop, topright, midright, bottomright, midbottom, bottomleft, midleft, center
            or a Vector (in sprite coordinates) defining the offset from the sprite center
        """
        try:
            return Vec(self._fixed_point.x, self._fixed_point.y, 1)
        except Exception as ex:
            return str(self._fixed_point)

    @anchor.setter
    def anchor(self, value):
        """
        Set the anchor, can be one of:
            'topleft', 'midtop', 'topright', 'midright', 'bottomright', 'midbottom', 'bottomleft', 'midleft', 'center'
            or a Vector (in sprite coordinates) defining the offset from the sprite center
        """
        if type(value) == type(self._fixed_point):
            if isinstance(value, str):
                if value == self._fixed_point:
                    return
            if isinstance(value, Vec):
                if value.x == self._fixed_point.x and value.y == self._fixed_point.y and value.w == self._fixed_point.w:
                    return

        if isinstance(value, Vec):
            self._fixed_point = Vec(value.x, value.y)
        else:
            self._fixed_point = value
        self.on_anchor_changed()

    def on_anchor_changed(self):
        """
        This is called when the anchor value has changed.
        """
        self._update()

    @property
    def rotation(self):
        """
        Returns the rotation angle of the sprite in degrees.
        """
        return self._rot

    @rotation.setter
    def rotation(self, value):
        """
        Set the rotation angle in degrees.
        :note: the angle will be rounded into the range [0, 360)
        :note: the padding area will be filled with black, therefore a colorkey for black will be set, so avoid black
            in the original image!
        """
        if value == self._rot:
            return
        # round to 1 degree precision to get an reasonable amount of cashing values
        self._rot = round(value % 360.0, self._rotation_precision)
        self.on_rotation_chaned()

    def on_rotation_chaned(self):
        """
        This is called when the rotation value has changed.
        """
        self._update()

    @property
    def flipped_x(self):
        """
        Return if the sprite is flipped in the x-axis.
        """
        return self._flip_x

    @flipped_x.setter
    def flipped_x(self, value):
        """
        If set to True, then it will be flipped along the x-axis.
        """
        if value == self._flip_x:
            return
        self._flip_x = value
        self.on_flipped_x_changed()

    def on_flipped_x_changed(self):
        """
        This is called when the flipped_x value has changed.
        """
        self._update()

    @property
    def flipped_y(self):
        """
        Return if the sprite is flipped in the y-axis.
        """
        return self._flip_y

    @flipped_y.setter
    def flipped_y(self, value):
        """
        If set to True, then it will be flipped along the y-axis.
        """
        if value == self._flip_y:
            return
        self._flip_y = value
        self.on_flipped_y_changed()

    def on_flipped_y_changed(self):
        """
        This is called when the flippped_y value has changed.
        """
        self._update()

    @property
    def zoom(self):
        """
        Returns the current zoom value (scale).
        """
        return self._zoom_factor

    @zoom.setter
    def zoom(self, value):
        """
        Set the zoom value.
        """
        if value == self._zoom_factor:
            return
        self._zoom_factor = round(value, self._zoom_precision)
        self.on_zoom_changed()

    def on_zoom_changed(self):
        """
        This is called when the zoom value has changed.
        """
        self._update()

    @property
    def alpha(self):
        """
        Returns the currently used alpha value
        """
        return self._alpha

    @alpha.setter
    def alpha(self, value):
        """
        Set the alpha transparency value.
        """
        self._alpha = value
        self.on_alpha_changed()

    def on_alpha_changed(self):
        """
        This is called when the zoom value has changed.
        """
        self._update()

    @property
    def z_layer(self):
        """
        Returns the z-layer of the sprite.
        """
        return self.internal_z

    @z_layer.setter
    def z_layer(self, value):
        """
        Set the z-layer of the sprite, causes a re-sort in the renderer.
        """

        if self.internal_z == value:
            return
        self.internal_z = value
        self.on_z_layer_changed()

    def on_z_layer_changed(self):
        """
        This is called when the z_layer value has changed.
        """
        pass

    def set_image(self, image):
        """
        Set a different image for this sprite. Use this method to make sure that the sprites
        rect and anchor point are adjusted correctly.

        :Parameters:
            image : Surface
                the new image to use.
        """
        if self._image == image:
            return
        self._image = image.copy()
        # self._image = image
        # self.rect = self.image.get_rect(center=self.rect.center)
        self.on_image_changed()

    def on_image_changed(self):
        """
        This is called when the image value has changed.
        """
        self._update()

    def _update(self):
        """
        Update the anchor point and update the image to draw. The anchor is converted to an vector offset
        and transformed according to the active transformations (flip, rotate, etc).

        :note: if 'draw_special' is True then this method does nothing.
        """

        if not self.draw_special:
            # todo: is there a better caching strategy???
            key = (self._image, self._rot, self._flip_x, self._flip_y, self._zoom_factor)
            if self._use_image_cache and key in self._image_cache:
                self.image = self._image_cache[key]
            else:
                if self._rot != 0 or self._zoom_factor != 1.0:
                    self.image = pygame.transform.rotozoom(self._image, self._rot, self._zoom_factor)
                    self.image.set_colorkey((0, 0, 0))
                    self.image = pygame.transform.flip(self.image, self._flip_x, self._flip_y)
                else:
                    self.image = pygame.transform.flip(self._image, self._flip_x, self._flip_y)

                self._image_cache[key] = self.image

                if len(self._image_cache) > self._max_cache_entry_count:
                    self._image_cache.clear()

            self.rect = self.image.get_rect(center=self.rect.center)

            if self.alpha < 255:
                self.image.fill((255, 255, 255, self._alpha), None, pygame.BLEND_RGBA_MULT)

            # anchor
            if isinstance(self._fixed_point, Vec):
                assert isinstance(self._fixed_point, Vec), \
                            "anchor has wrong type, expected <Vector>, not " + str(type(self._fixed_point)) 
                self.offset = Vec(self._fixed_point.x, self._fixed_point.y)
            else:
                assert self._fixed_point in ('topleft', 'midtop', 'topright', 'midright', 'bottomright', 'midbottom', \
                    'bottomleft', 'midleft', 'center'), "wrong attribute name should be one of: 'topleft'," +\
                    " 'midtop', 'topright', 'midright', 'bottomright', 'midbottom', 'bottomleft', 'midleft', 'center'"
                if self.rect:
                    width, hight = self.rect.size
                    offx, offy = getattr(pygame.Rect(0, 0, width, hight ), self._fixed_point)
                    offx -= width / 2.0
                    offy -= hight / 2.0
                    self.offset = Vec(offx, offy)

            if self._rot != 0.0:
                # todo: why negative?? should the vector rotation be inversed?
                self.offset.rotate(-self._rot)
            if self._flip_x:
                self.offset.x = -self.offset.x
            if self._flip_y:
                self.offset.y = -self.offset.y
            if self._zoom_factor != 1.0:
                self.offset = self.offset * self._zoom_factor

    def configure_image_cache(self, use_image_cache, rotation_precision=0, zoom_precision=2, \
                                max_cache_entry_count=1000, single_sprite=True):
        """
        Configure the image cache, either for a single sprite or globally for all sprites.


        :Parameters:
            use_image_cache[False] : bool
                if set to True the internal image cache will be used, otherwise nothing is cached (default).
            rotation_precision[0]: int
                round to that number of digits after the decimal point for the rotation
            zoom_precision[2] : int
                round to that number of digits after the decimal point for the scaling factor
            max_cache_entry_count[1000] : int
                number of entries in the cache, if that number is reached the cache will be cleared
            single_sprite[True] : bool
                if True apply configuration to a single sprite, otherwise it will be applied globally to all sprites.
                Note that when setting those values globally, individual sprites could still have different individual
                settings.

        """
        if single_sprite:
            self._use_image_cache = use_image_cache
            self._rotation_precision = rotation_precision
            self._zoom_precision = zoom_precision
            self._max_cache_entry_count = max_cache_entry_count
        else:
            Sprite._use_image_cache = use_image_cache
            Sprite._rotation_precision = rotation_precision
            Sprite._zoom_precision = zoom_precision
            Sprite._max_cache_entry_count = max_cache_entry_count

    def blit(self, surf, dest, area=None, special_flags=0):
        """
        Blits the image onto surf.

        :Parameters:
            surf : Surface
                surface to blit onto
            dest : pair of coordinates or Rect
                can be either a pair of coordinates representing the upper left corner or a rect
            area[None] : Rect
                A area of the source image. If it defaults to None, then the area of this sprite will be used.
            special_flags[0] : const
                This is one of the BLEND_* or BLEND_RGBA_* constants. If default value 0 is passed, then the
                surf_flags of this sprite will be used.

        :returns: Rectangle affected by the blit.
        """
        rect = self.image.get_rect()
        rect.centerx = dest[0] - self.offset.x
        rect.centery = dest[1] - self.offset.y
        return surf.blit(self.image, rect, area if area is not None else self.area, \
                                            special_flags if special_flags > 0 else self.surf_flags)

    def draw(self, surf, cam, interpolation_factor=1.0):
        """
        If 'draw_special' is set to True, then this method will be called from the renderer. Here special draw
        code can be implemented. All coordinate transformations have to be done here (simplest is to use the
        cam.world_to_screen() method). It should return a rect in screen coordinates that will be used for drawing
        and picking.

        :returns: rect in screen coordinates (returns a Rect(0, 0, 0, 0) by default)

        :Parameters:
            surf : Surface
                the surface to draw onto
            cam : Camera
                the camera that is used to render, determines the world position and the screen area
            interpolation_factor[1.0] : float
                the interpolation factor to use, should be in the range [0, 1.0), other values might work too but
                the result might be unwanted
        """
        return pygame.Rect(0, 0, 0, 0)

class TextSprite(Sprite):
    """
    A sprite for displaying text. It inherits from sprite and the only difference is that
    a string is provided instead of an image.
    """

    font = None
    antialias = 10
    color = (255, 255, 255)
    backcolor = None

    def __init__(self, text, position, font=None, antialias=None, color=None, backcolor=None, \
                                        anchor=None, z_layer=1000, parallax_factors=None):
        """

        :Parameters:
            text : string
                the text to display
            position : Vector
                where it should be, in world coordinates
            font[pygame default font] : pygame.font.Font
                the font to use
            antialias[10] : int
                the antialias factor as for the pygame.font.Font
            color[(255, 255, 255)] : tuple
                the color for the text
            backcolor[None] : tuple
                background color for the text, it will be transparent if None is provided
            anchor['center'] : rect attribute name or Vector
                anchor point, defaults to 'center' but can be one of following:
                    topleft, midtop, topright, midright, bottomright, midbottom, bottomleft, midleft, center
                    or a Vector (in sprite coordinates) defining the offset from the sprite center
            z_layer[1000] : float or int
                the layer this sprite is in, lower values is 'farther away'
            parallax_factors[Vector(1.0, 1.0)] : Vector
                a vector containin two floats, normally in the range [0.0, 1.0], if set to 0.0 then
                the sprite will not scroll (kinda attacked to the camera), this works good for single
                screen games using one camera (for split screen with multiple cameras this wont work because
                each screen should have its own HUD sprites, therefore the HUDRenderer should be used)

        """
        if self.font is None:
            TextSprite.font = pygame.font.Font(None, 20)
        if font is not None:
            self.font = font
        if antialias is not None:
            self.antialias = antialias
        if color is not None:
            self.color = color
        if backcolor is not None:
            self.backcolor = backcolor
        self._text = ""
        image = self.render()
        Sprite.__init__(self, image, position, anchor=anchor, \
                                               z_layer=z_layer, parallax_factors=parallax_factors)
        self.text = text

    def _get_text(self):
        """
        Get the text.
        """
        return self._text

    def _set_text(self, value):
        """
        Set the text.
        """
        self._text = value
        self._image = self.render()
        self.on_text_changed()
        
    text = property(_get_text, _set_text, doc="""Gets or sets the text.""")

    def on_text_changed(self):
        """
        This is called when the text changes.
        """
        self._update()

    def render(self):
        """
        Renders the text using the attributes saved onto an image and returns it.

        :Returns: surface with the rendered text.
        """
        if self.backcolor is None:
            img = self.font.render(self.text, self.antialias, self.color)
        else:
            img = self.font.render(self.text, self.antialias, self.color, self.backcolor)

        return img


class VectorSprite(Sprite):
    """
    Draws a vector sprite. The ideas was to visualize a vector. The position attribute of
    the vector will be used as starting point to draw.

    This class will use the special_draw ability of the renderer and call its draw method.
    Therefore it might not be that performant.

    """


    color = (255, 255, 255)

    # @staticmethod
    # def from_vector(v, color=None, visible=True, z_layer=1000, label=None):
        # return VectorSprite(v.x, v.y, v.w, v.position, color, visible, z_layer, label)

    # def __init__(self, vec, position=Vec(0.0, 0.0), color=None, z_layer=999, label=None, parallax_factors=None):
    def __init__(self, vec, position, color=None, z_layer=999, label=None, parallax_factors=None):
        """
        :Parameters:
            vec : Vector
                the vector to draw
            position[Vec(0, 0)] : Vector
                position of the where the vector origin should be drawn, default to 0, 0, the coordinates origin
            color[(255, 255, 255)] : tuple
                the color to draw the vector
            z_layer[999] : float
                the layer this vector will be drawn
            label[None] : TextSprite
                the text to draw near the vector, for a vector the label.position is relative to
                the position, if the vector is a point then the label.position is relative
                to the vector
        """
        self.vector = vec
        self.draw_special = True
        self.label = label
        self.position = Point2(position.x, position.y)
        Sprite.__init__(self, None, self.position, z_layer=z_layer, parallax_factors=parallax_factors)
        if color is not None:
            self.color = color
        if label:
            if self.vector.w == TYPEPOINT:
                self.label_offset = self.label.position - self.position
                self.label.position = self.vector + self.label_offset
            else:
                self.label.position = self.position + (self.label.position - Point2())

    def draw(self, surf, cam, interpolation_factor=1.0):
        """
        Draws this vector.

        :Parameters:
            surf : Surface
                the surface to draw the vector on
            cam : Camera
                the cam to use to draw
            interpolation_factor[1.0] : float
                the interpolation factor to use, should be in the range [0, 1.0), other values might work too but
                the result might be unwanted

        """
        if self.vector.w == TYPEVECTOR:
            wpos = cam.world_to_screen(self.position)
            pointlist = []
            pointlist.append((wpos.x, wpos.y))
            tip = wpos + self.vector
            pointlist.append((tip.x, tip.y))
            rotated = Vec(-self.vector.y, self.vector.x)
            vpos = (wpos + self.vector * 0.75 + rotated * 0.125 )
            pointlist.append((vpos.x, vpos.y))
            vpos = (wpos + 0.75 * self.vector - 0.125 * rotated)
            pointlist.append((vpos.x, vpos.y))
            pointlist.append((tip.x, tip.y))

            self.rect = pygame.draw.aalines(surf, self.color, False, pointlist)
        else:
            vpos = cam.world_to_screen(self.vector)
            # self.rect = pygame.draw.circle(surf, self.color, (int(vpos.x), int(vpos.y)), 2)
            surf.set_at(vpos.as_tuple(int), self.color)
            self.rect = pygame.Rect(vpos.as_tuple(int), (1, 1))
        return self.rect



# from math import sin, cos, atan2, radians, degrees, hypot

# class Vector(object):
    # """
    # 2D vector class.

    # w : TYPEPOINT = 1 represents a point
        # TYPEVECTOR = 0 represents a vector

    # v1, v2 vectors

    # operators:
    # v1 + v2   addition
    # v1 - v2   subtraction
    # v1 % v2   elementwise multiplication
    # v1 ^ v2   cross product (not implemented)
    # v1 | v2   dot product
    # ~v1      elementwise invert

    # """
    # # this will be set after class definition because otherwise
    # # there would be a recursive call to this calss construction
    # position = None

    # def __init__(self, x=0.0, y=0.0, w=TYPEVECTOR, position=None):
        # """
        # x : x component of the vector
        # y : y component of the vector
        # w[TYPEVECTOR=1] : 1 represents a point
            # 0 represents a vector
        # position[Vector(0,0,w=1)] : a point, where the vector is attached in the room (like forces in physics)
        # """
        # if position is not None:
            # assert TYPEPOINT == position.w
            # self.position = Vector(position.x, position.y, TYPEPOINT)
        # self.x = x
        # self.y = y
        # self.w = w

    # @staticmethod
    # def from_points(to_point, from_point):
        # """
        # Returns the vector between the two given points. The position attribute of the resulting vector
        # is set to the position of the from_point. The resulting vector will then point to the to_point.

        # The difference to simply substract the points is the position argument.

        # """
        # assert to_point.w == TYPEPOINT, "to_point isn't a point according to w"
        # if from_point:
            # assert from_point.w == TYPEPOINT, "from_point isn't a point according to w"
            # return Vector(to_point.x - from_point.x, to_point.y - from_point.y, \
                                                            # w=TYPEVECTOR, position=from_point.copy())
        # return Vector(to_point.x, to_point.y)

    # @staticmethod
    # def from_tuple(tup, w=TYPEVECTOR, position=None):
        # """
        # Return a Vector instance from a tuple.

        # :Parameters:
            # tup : tuple
                # the tuple to use, only the first two values are used.
            # w[TYPEVECTOR] : TYPEVECTOR, TYPEPOINT
                # optional w argument to define if it is a point or a vector
            # position[None] : Vector
                # the position of the vector
        # """
        # return Vector(tup[0], tup[1], w=w, position=position)

    # def copy(self):
        # """
        # Returns a new instance of vector with the same values for all attributes.
        # """
        # return Vector(self.x, self.y, w=self.w, position=Vector(self.position.x, self.position.y, self.position.w))

    # def round_ip(self, round_func=round, *args):
        # """
        # Rounds the components of this vector in place using the rounding function.

        # Example::

            # v = Vector(1.75, 3.33)
            # v.round_ip() # v == Vector(2, 3)
            # w = Vector(1.75, 3.33)
            # w.round(round, 1) # w == Vector(1.8, 3.3)
            # x = Vector(1.75, 3.33)
            # x.round(int) # x == Vector(1, 3)

        # :Parameters:
            # round_func[round] : function
                # the rounding function to use, defaults to round
            # args : args
                # the arguments to use for the rounding function.
        # """
        # self.x = round_func(self.x, *args)
        # self.y = round_func(self.y, *args)
        # return self

    # def round(self, round_func=round, *args):
        # """
        # Returns a new vector with rounded components using the rounding function.

        # Example::

            # v = Vector(1.75, 3.33)
            # w = v.round() # w == Vector(2, 3)
            # x = v.round(round, 1) # x == Vector(1.8, 3.3)
            # y = v.round(int) # y == Vector(1, 3)

        # :Parameters:
            # round_func[round] : function
                # the rounding function to use, defaults to round
            # args : args
                # the arguments to use for the rounding function.
        # """
        # return Vec(round_func(self.x, *args), round_func(self.y, *args), w=self.w, position=self.position.copy())

    # def rotate(self, deg):
        # """
        # Returns a rotated the vector.

        # :Parameters:
            # deg : int
                # degrees to rotate, deg > 0 : anti-clockwise rotation
        # """
        # if deg == 0:
            # rotated_x = self.x
            # rotated_y = self.y
        # elif deg == 90:
            # rotated_x = self.y
            # rotated_y = -self.x
        # elif deg == -90:
            # rotated_x = -self.y
            # rotated_y = self.x
        # elif deg == 180:
            # rotated_x = -self.x
            # rotated_y = -self.y
        # else:
            # rad = radians(deg)
            # cval = cos(rad)
            # sval = sin(rad)
            # rotated_x = self.x * cval + self.y * sval
            # rotated_y = -self.x * sval + self.y * cval
        # return Vector(rotated_x, rotated_y, w=self.w, position=self.position.copy())

    # def rotate_ip(self, deg):
        # """
        # Rotates this vector in place.

        # :Parameters:
            # deg : int
                # degrees to rotate, deg > 0 : anti-clockwise rotation
        # """
        # rad = radians(deg) # use PI_DIV_180
        # rotated_x = self.x * cos(rad) + self.y * sin(rad)
        # self.y = -self.x * sin(rad) + self.y * cos(rad)
        # self.x = rotated_x
        # return self

    # @property
    # def angle(self):
        # """
        # Returns the angle between this vector and the x-axis.
        # """
        # return -degrees(atan2(self.y, self.x))

    # @property
    # def length(self):
        # """
        # Returns the length of the vector.
        # """
        # return hypot(self.x, self.y)

    # @length.setter
    # def length(self, value):
        # """
        # Set the length of this vector.
        # """
        # self.normalize_ip()
        # self.x *= value
        # self.y *= value

    # def normalize(self):
        # """
        # Returns a normalized (unit length) vector.
        # """
        # length = self.length
        # if length > 0:
            # return Vector(self.x / length, self.y / length, w=self.w, position=self.position.copy())
        # return Vector(w=self.w, position=self.position.copy())

    # def normalize_ip(self):
        # """
        # Does normalize this vector in place.
        # """
        # length = self.length
        # if length > 0:
            # self.x /= length
            # self.y /= length
        # return self

    # # def __getitem__(self, idx):
        # # if idx == 1:
            # # return self.x
        # # elif idx == 2:
            # # return self.y
        # # raise IndexError(str(idx))

    # # def __getslice__(self, idx, idx2):
        # # return (self.x, self.y)

    # def __call__(self, round_func=None, *args):
        # """
        # Returns a tuple with the values of this vector.

        # Example::
            # v = Vector(1.7, 3.3)
            # t = v() # t == (1.7, 3.3)
            # t = v(int) # t == (1, 3)
            # t = v(round) # t == (2, 3)


        # :Parameters:
            # round_func[None] : func
                # the tuple values can be rounded by the rounding function to use
            # args : args
                # the arguments for the rounding function
        # """
        # if round_func:
            # return (round_func(self.x, *args), round_func(self.y, *args))
        # return (self.x, self.y)

    # def __mul__(self, scalar):
        # """
        # Returns a scaled vector. Only scalar multiplication.

        # a * v == Vector(a * v.x, a * v.y)

        # Example:
            # a = 2
            # v = Vector(1, 3)
            # w = a * v  # Vector(2, 6)
            # x = v * a  # Vector(2, 6)

        # """
        # return Vector(scalar * self.x, scalar * self.y, w=self.w, position=self.position.copy())

    # __rmul__ = __mul__

    # def __sub__(self, other):
        # """
        # Vector subtraction.

        # v - w == Vector(v.x - w.x, v.y - w.y)

        # p1 - p2 => v1 -> w = 0
        # p1 - v1 => p2 -> w = 1
        # v1 - v2 => v3 -> w = 0
        # v1 - p1 => error ( w =  -1)
        # """
        # assert (self.w - other.w) in [TYPEVECTOR, TYPEPOINT] and \
                # self.w - other.w == (TYPEPOINT if (self.w == TYPEPOINT and \
                # other.w == TYPEVECTOR) else TYPEVECTOR), \
                # "trying to substract a point " + str(other) + " from a vector" + str(self)
        # return Vector(self.x - other.x, self.y - other.y, w=self.w - other.w)

    # def __add__(self, other):
        # """
        # Vecotr addition.

        # v + w == Vector(v.x + w.x, v.y + w.y)

        # p1 + p2 => error ( w = 2)
        # p1 + v1 => p2 -> w = 1
        # v1 + v2 => v3 -> w = 0
        # v1 + p1 => same as p1 + v1 ( w =  1)

        # """
        # assert (self.w + other.w) in [TYPEVECTOR, TYPEPOINT] and \
                # self.w + other.w == TYPEVECTOR if (self.w == TYPEVECTOR and \
                # other.w == TYPEVECTOR) else TYPEPOINT, "trying to add two points"
        # assert 0 <= self.w + other.w <= 1, "adding two points? " + str(self) + " + " + str(other)
        # return Vector(self.x + other.x, self.y + other.y, w=self.w + other.w)

    # def __mod__(self, other):
        # """
        # Elementwise multiplication.

        # v % w == Vector(v.x * w.x, v.y * w.y, v.w * w.w)

        # p1 % p2 => error ( w = 1)
        # p1 % v1 => p2 -> w = 0
        # v1 % v2 => v3 -> w = 0
        # v1 % p1 => v2 -> w = 0
        # """
        # # assert self.w == TYPEPOINT, 'scaling a point does not make sense'
        # return Vector(self.x * other.x, self.y * other.y, w=self.w)

    # # def __xor__(self, other): # cross
        # # """
        # # Vector cross product.

        # # v ^ w == v cross w
        # # """
        # # raise NotImplementedError
        # # # return Vec3(v.y * w.z - w.y * v.z, v.z * w.x - w.z * v.x, v.x * w.y - w.x * v.y)

    # def __or__(self, other): # dot
        # """
        # Vector dot product.

        # v | w == v.x * w.x + v.y * w.y

        # """
        # assert TYPEVECTOR == self.w and TYPEVECTOR == other.w
        # return self.x * other.x + self.y * other.y

    # def __invert__(self):
        # """
        # Elementwise invert.

        # v = Vector(a, b)
        # ~v # Vector( 1.0 / a, 1.0 / b, w)
        # """
        # assert TYPEVECTOR == self.w
        # return Vector(1.0 / self.x, 1.0 / self.y, w=self.w)

    # def is_vector(self):
        # """
        # Returns if it is used as vector.
        # """
        # return (TYPEVECTOR == self.w)

    # def is_point(self):
        # """
        # Returns if it is used as point.
        # """
        # return (TYPEPOINT == self.w)

    # def __str__(self):
        # """
        # String representation of the vector.
        # """
        # # nicer format for vectors
        # return "<{0}[{1}, {2}, w={3}]>".format(self.__class__.__name__, self.x, self.y, self.w)

    # def __repr__(self):
        # """
        # Representation of the vector using __repr__ everywhere.
        # """
        # # <__main__.Vector object at 0x00C13CF0>
        # return "<{0}[{1}, {2}, w={3}] object at {4}>".format(self.__class__.__name__, repr(self.x), \
                                                                        # repr(self.y), self.w, hex(id(self)))

# Vector.position = Vector(0, 0, w=TYPEVECTOR)
# Vector.position.w = TYPEPOINT

# Vec = Vector
